package x.ovo.wechat.bot.impl.core;


import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.dromara.hutool.core.io.file.FileUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.xml.XmlUtil;
import org.dromara.hutool.http.html.HtmlUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import x.ovo.wechat.bot.core.Constant;
import x.ovo.wechat.bot.core.Context;
import x.ovo.wechat.bot.core.contact.ContactManager;
import x.ovo.wechat.bot.core.contact.RetrievalType;
import x.ovo.wechat.bot.core.entity.Member;
import x.ovo.wechat.bot.core.enums.MessageType;
import x.ovo.wechat.bot.core.http.WechatApi;
import x.ovo.wechat.bot.core.message.*;
import x.ovo.wechat.bot.core.util.WechatUtil;
import x.ovo.wechat.bot.impl.config.ClientConfig;

import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

/**
 * 消息工厂
 *
 * @author ovo on 2024/07/18.
 */
@Slf4j(topic = "MessageFactory")
@UtilityClass
public class MessageFactory {

    private final Context ctx = Context.INSTANCE;
    private final ClientConfig config = ClientConfig.get();
    private final WechatApi api = ctx.getApi();
    private final ContactManager manager = ctx.getContactManager();
    private final Map<MessageType, MessageConvertor> map = new HashMap<>() {{
        put(MessageType.TEXT, textMessageConvertor());
        put(MessageType.IMAGE, imageMessageConvertor());
        put(MessageType.VOICE, voiceMessageConvertor());
        put(MessageType.VIDEO, videoMessageConvertor());
        put(MessageType.EMOTICON, emoticonMessageConvertor());
        put(MessageType.SYSTEM, systemMessageConvertor());
        put(MessageType.RECALLED, revolvedMessageConvertor());
        put(MessageType.VERIFY, verifyMessageConvertor());
        put(MessageType.SHARING, sharingMessageConvertor());
    }};

    public Message create(RawMessage rawMessage) {
        MessageType type = MessageType.get(rawMessage.getMsgType());
        Message message = Optional.ofNullable(map.get(type)).orElse(defaultMessageConvertor()).convert(rawMessage);
        fillFields(rawMessage, message, type);
        return message;
    }

    /**
     * 填写字段
     *
     * @param rawMessage 原始消息
     * @param message    消息
     * @param type       类型
     */
    private void fillFields(RawMessage rawMessage, Message message, MessageType type) {
        message.setId(rawMessage.getMsgId());
        message.setRaw(rawMessage);
        message.setType(type);
        message.setFrom(ctx.getContactManager().get(rawMessage.getFromUserName()));
        message.setTo(ctx.getContactManager().get(rawMessage.getToUserName()));
        message.setContent(rawMessage.getContent());

        // 如果消息是群聊，且消息正文包含群消息分隔符，则需要去除消息分隔符，获取发送消息的群成员信息
        if (rawMessage.isGroup() && rawMessage.getContent().contains(Constant.GROUP_BR)) {
            String[] split = rawMessage.getContent().split(Constant.GROUP_BR);
            // 获取发送消息的群成员信息
            Member member = manager.get(rawMessage.getFromUserName(), split[0], RetrievalType.USER_NAME);
            if (Objects.nonNull(member)) {
                message.setMember(member);
                message.setContent(split.length > 1 ? split[1] : "");
            }
        }
        // 消息正文需要进行html字符串转义后进行格式化，去除空格、unicode控制符和转换emoji
        String content = StrUtil.defaultIfBlank(message.getContent(), rawMessage.getContent());
        message.setContent(WechatUtil.format(HtmlUtil.unescape(content)));
    }

    private MessageConvertor defaultMessageConvertor() {
        return r -> new Message();
    }

    private MessageConvertor textMessageConvertor() {
        return r -> {
            TextMessage message = new TextMessage();
            message.setAtMe(r.getContent().contains("@" + ctx.getSession().getNickName()));
            return message;
        };
    }

    private MessageConvertor imageMessageConvertor() {
        return r -> {
            ImageMessage message = new ImageMessage();
            message.setWidth(r.getImgWidth());
            message.setHeight(r.getImgHeight());
            if (config.getSaveMedia()) {
                String dir = r.isGroup() ? ctx.getContactManager().get(r.getFromUserName()).getNickName() : "Friend";
                File file = FileUtil.file(Constant.Files.IMAGE_DIR, dir, r.getMsgId() + ".jpg");
                byte[] bytes = api.getImage(r.getMsgId());
                message.setBytes(bytes);
                FileUtil.writeBytes(bytes, file);
            }
            return message;
        };
    }

    private MessageConvertor videoMessageConvertor() {
        return r -> {
            VideoMessage message = new VideoMessage();
            message.setLength(r.getPlayLength());
            if (config.getSaveMedia()) {
                String dir = r.isGroup() ? ctx.getContactManager().get(r.getFromUserName()).getNickName() : "Friend";
                File file = FileUtil.file(Constant.Files.VIDEO_DIR, dir, r.getMsgId() + ".mp4");
                byte[] bytes = api.getVideo(r.getMsgId());
                message.setBytes(bytes);
                FileUtil.writeBytes(bytes, file);
            }
            return message;
        };
    }

    private MessageConvertor voiceMessageConvertor() {
        return r -> {
            VoiceMessage message = new VoiceMessage();
            message.setLength(r.getVoiceLength());
            if (config.getSaveMedia()) {
                String dir = r.isGroup() ? ctx.getContactManager().get(r.getFromUserName()).getNickName() : "Friend";
                File file = FileUtil.file(Constant.Files.VOICE_DIR, dir, r.getMsgId() + ".mp3");
                byte[] bytes = api.getVoice(r.getMsgId());
                message.setBytes(bytes);
                FileUtil.writeBytes(bytes, file);
            }
            return message;
        };
    }

    private MessageConvertor emoticonMessageConvertor() {
        return r -> {
            EmoteMessage message = new EmoteMessage();
            message.setEmoticonMd5(r.getContent());
            return message;
        };
    }

    private MessageConvertor systemMessageConvertor() {
        return r -> new SystemMessage();
    }

    private MessageConvertor revolvedMessageConvertor() {
        return r -> {
            RevokeMessage message = new RevokeMessage();
            message.setRevokedMessageId(r.getContent());
            message.setPlaceholder(r.getContent());
            return message;
        };
    }

    private MessageConvertor verifyMessageConvertor() {
        return r -> {
            VerifyMessage message = new VerifyMessage();
            message.setRecommend(r.getRecommendInfo());
            return message;
        };
    }

    private MessageConvertor sharingMessageConvertor() {
        return msg -> {
            SharedMessage message = new SharedMessage();
            String content = HtmlUtil.unescape(msg.getContent()).replaceFirst("@\\w+:<br/>", "").replaceAll("<br/>", "").replaceAll("\t", "");
            if ("该类型暂不支持，请在手机上查看".equals(content)) {
                message.setContent(content);
                message.setShareType(SharedMessage.Type.UNKNOWN);
                message.setTitle("未知类型");
                message.setDescription("未知的分享类型，可能是聊天记录");
                return message;
            }
            Document document = XmlUtil.parseXml(content);
            Element rootElement = XmlUtil.getRootElement(document);
            Element appmsg = XmlUtil.getElement(rootElement, "appmsg");
            SharedMessage.Type type = SharedMessage.Type.get(Integer.parseInt(XmlUtil.elementText(appmsg, "type")));

            // 设置分享消息属性
            message.setShareType(type);
            message.setTitle(XmlUtil.elementText(appmsg, "title"));
            // 微视频分享消息
            if (SharedMessage.Type.MICRO_VIDEO.equals(type)) {
                Element finderFeed = XmlUtil.getElement(appmsg, "finderFeed");
                message.setDescription(XmlUtil.elementText(finderFeed, "desc"));
                return message;
            }
            message.setDescription(XmlUtil.elementText(appmsg, "des").replaceAll("\n", " "));
            message.setUrl(XmlUtil.elementText(appmsg, "url"));
            return message;
        };
    }

}
